#include "IdInfoContainer.h"

#include <iostream>

#include <clang/AST/Expr.h>

IdInfoContainer::IdInfoContainer(clang::SourceManager *sm) :
	m_sm(sm),
	m_printingPolicy(clang::LangOptions())
{}

void IdInfoContainer::markUsedIds(const std::list<std::string> &idList) {
	for (std::list<std::string>::const_iterator i = idList.begin(); i != idList.end(); ++i) {
		markUsedId(*i);
	}
}


void IdInfoContainer::markUsedIds(const llvm::cl::list<std::string> &idList) {
	for (llvm::cl::list<std::string>::const_iterator i = idList.begin(); i != idList.end(); ++i) {
		markUsedId(*i);
	}
}


void IdInfoContainer::markUsedId(const std::string &name) {
	LinkedIdentifierInfoMap::iterator i = m_idMap.find(name);
	if (i != m_idMap.end()) {
		markUsedId(&(i->second));
	}
}

int upper(int c) {
	return std::toupper((unsigned char)c);
}


void IdInfoContainer::print(llvm::raw_ostream &os) {
	std::string macro = m_sm->getFileEntryForID(m_sm->getMainFileID())->getName();
	std::transform(macro.begin(), macro.end(), macro.begin(), upper);
	int i;

	macro = "__" + macro.substr(0, macro.size() - 2) + "_H";
	while ((i = macro.find('.')) != macro.npos) {
		macro[i] = '_';
	}
	while ((i = macro.find('/')) != macro.npos) {
		macro[i] = '_';
	}
	os << "#ifndef " << macro << "\n#define " << macro << "\n\n\n";


	for (HeaderInfoMap::iterator i = m_headerMap.begin(); i != m_headerMap.end(); ++i) {
		if (i->second.m_isUsed) {
			if (i->second.m_isAngled) {
				os << "#include <" << i->second.m_nameAsWritten << ">\n";
			}
			else {
				os << "#include \"" << i->second.m_nameAsWritten << "\"\n";
			}
		}
	}

	os << "\n\n";

	for (LinkedIdentifierInfoList::iterator i = m_idList.begin(); i != m_idList.end(); ++ i) {
		if ((*i)->m_isUsed) {
			clang::Decl *D = (*i)->m_pDecl;
			clang::FunctionDecl *funcDecl = llvm::dyn_cast_or_null<clang::FunctionDecl>(D);
			clang::VarDecl *varDecl = llvm::dyn_cast_or_null<clang::VarDecl>(D);
			if (funcDecl) {
				printFunctionPrototype(os, funcDecl);
			}
			else if (varDecl) {
				printExternVarDecl(os, varDecl);
			}
			else {
				D->print(os, m_printingPolicy);
			}
			os << ";\n\n";
		}
	}


	os << "\n#endif /* ! " << macro << " */\n";
}


void IdInfoContainer::markUsedId(LinkedIdentifierInfo *id) {
	if (id->m_includeFileName == m_sm->getFileEntryForID(m_sm->getMainFileID())->getName()) {
		if (!id->m_isUsed) {
			id->m_isUsed = true;
			std::list<LinkedIdentifierInfo *>::iterator dep;
			for (dep = id->m_deps.begin(); dep != id->m_deps.end(); ++ dep) {
				markUsedId(*dep);
			}
		}
	}
	else {
		m_headerMap[id->m_includeFileName].m_isUsed = true;
	}
}


void IdInfoContainer::printFunctionPrototype(llvm::raw_ostream &os, clang::FunctionDecl *D) {
	using namespace clang;

	switch (D->getStorageClass()) {
	case SC_None: break;
	case SC_Extern: os << "extern "; break;
	case SC_Static: os << "static "; break;
	case SC_PrivateExtern: os << "__private_extern__ "; break;
	default: break;
	}

	std::string proto = D->getNameInfo().getAsString();

	QualType Ty = D->getType();
	while (const ParenType *PT = dyn_cast<ParenType>(Ty)) {
		proto= '(' + proto + ')';
		Ty = PT->getInnerType();
	}

	if (isa<FunctionType>(Ty)) {
		const FunctionType *AFT = Ty->getAs<FunctionType>();
		const FunctionProtoType *FT = 0;
		if (D->hasWrittenPrototype())
			FT = dyn_cast<FunctionProtoType>(AFT);

		proto += "(";
		if (FT) {
			llvm::raw_string_ostream POut(proto);
			for (unsigned i = 0, e = D->getNumParams(); i != e; ++i) {
				if (i) POut << ", ";
				D->getParamDecl(i)->print(POut, m_printingPolicy);
			}

			if (FT->isVariadic()) {
				if (D->getNumParams()) POut << ", ";
				POut << "...";
			}
		} else if (D->isThisDeclarationADefinition() && !D->hasPrototype()) {
			for (unsigned i = 0, e = D->getNumParams(); i != e; ++i) {
				if (i)
					proto += ", ";
				proto += D->getParamDecl(i)->getNameAsString();
			}
		}
		proto += ")";

		if (FT && FT->getTypeQuals()) {
			unsigned TypeQuals = FT->getTypeQuals();
			if (TypeQuals & Qualifiers::Const)
				proto += " const";
			if (TypeQuals & Qualifiers::Volatile)
				proto += " volatile";
			if (TypeQuals & Qualifiers::Restrict)
				proto += " restrict";
		}

		if (D->hasAttr<NoReturnAttr>())
			proto += " __attribute((noreturn))";
		AFT->getResultType().getAsStringInternal(proto, m_printingPolicy);
	} else {
		Ty.getAsStringInternal(proto, m_printingPolicy);
	}

	os << proto;
}

void IdInfoContainer::printVarDecl(llvm::raw_ostream &os, clang::VarDecl *D) {
	using namespace clang;

	if (D->getStorageClass() != SC_None)
		os << VarDecl::getStorageClassSpecifierString(D->getStorageClass()) << " ";

	std::string Name = D->getNameAsString();
	QualType T = D->getType();
	if (ParmVarDecl *Parm = llvm::dyn_cast_or_null<ParmVarDecl>(D))
		T = Parm->getOriginalType();
	T.getAsStringInternal(Name, m_printingPolicy);
	os << Name;
}


void IdInfoContainer::printExternVarDecl(llvm::raw_ostream &os, clang::VarDecl *D) {
	if (D->getStorageClass() != clang::SC_Extern) {
		os << "extern ";
	}
	printVarDecl(os, D);
}
